package com.wosiliujing.learning.controller;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wosiliujing.learning.constant.CommonConstants;
import com.wosiliujing.learning.constant.SecurityConstants;
import com.wosiliujing.learning.entity.SecurityUser;
import com.wosiliujing.learning.util.RestResult;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/***
 *
 * @author 刘靖
 * @date 2020/6/4 17:20
 */
@RestController
@AllArgsConstructor
@RequestMapping("/token")
public class TokenController {

    private final TokenStore tokenStore;

    private final RedisTemplate redisTemplate;

    private final ClientDetailsService clientDetailsService;

    @GetMapping("/login")
    public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
        modelAndView.setViewName("ftl/login");
        modelAndView.addObject("error", error);
        return modelAndView;
    }

    /**
     * 确认授权页面
     */
    @GetMapping("/confirm_access")
    public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
        Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
        modelAndView.addObject("scopeList", scopeList.keySet());

        Object auth = session.getAttribute("authorizationRequest");
        if (auth != null) {
            AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            SecurityUser SecurityUser = (SecurityUser) authentication.getPrincipal();
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
            modelAndView.addObject("app", clientDetails.getAdditionalInformation());
            modelAndView.addObject("user", SecurityUser);
        }

        modelAndView.setViewName("ftl/confirm");
        return modelAndView;
    }

    /**
     * 退出登陆
     * @param oauthHeader
     * @return
     */
    @DeleteMapping("/logOut")
    public RestResult logOut(@RequestHeader(value = HttpHeaders.AUTHORIZATION ,required = false ) String oauthHeader){
        if(StrUtil.isNotBlank(oauthHeader)){
            String tokenValue = oauthHeader.replace(OAuth2AccessToken.BEARER_TYPE,StrUtil.EMPTY).trim();
            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(tokenValue);
            if(ObjectUtil.isNotEmpty(oAuth2AccessToken)&&StrUtil.isNotBlank(oAuth2AccessToken.getValue())){
                tokenStore.removeAccessToken(oAuth2AccessToken);
                tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken());
              //  httpSession.invalidate();
                return RestResult.ok();
            }
        }
        return RestResult.error();
    }

    /**
     *
     * @param token
     * @return
     */
    @DeleteMapping("/{token}")
    public RestResult removeToken(@PathVariable String token){
        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
        if(ObjectUtil.isNotEmpty(oAuth2AccessToken)&&StrUtil.isNotBlank(oAuth2AccessToken.getValue())){
            tokenStore.removeAccessToken(oAuth2AccessToken);
            tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken());
            return RestResult.ok();
        }
        return RestResult.error("该用户已经下线");
    }


    /**
     * 分页参数
     * @param params 分页参数
     * @return
     */
    @GetMapping("/page")
    public RestResult getTokenPage(@RequestParam Map<String, Object> params) {
        List<Map<String, String>> list = new ArrayList<>();
        if (StringUtils.isEmpty(MapUtil.getInt(params, CommonConstants.CURRENT)) || StringUtils.isEmpty(MapUtil.getInt(params, CommonConstants.CURRENT))) {
            params.put(CommonConstants.CURRENT, 1);
            params.put(CommonConstants.SIZE, 20);
        }
        //根据分页参数获取对应数据
        List<String> pages = findKeysForPage(SecurityConstants.TOKEN_PERFIX_ACCESS + "*", MapUtil.getInt(params, CommonConstants.CURRENT), MapUtil.getInt(params, CommonConstants.SIZE));

        for (String page : pages) {
            String accessToken = StrUtil.subAfter(page, SecurityConstants.TOKEN_PERFIX_ACCESS, true);
            OAuth2AccessToken token = tokenStore.readAccessToken(accessToken);
            Map<String, String> map = new HashMap<>(8);


            map.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
            map.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
            map.put(OAuth2AccessToken.EXPIRES_IN, token.getExpiresIn() + "");


            OAuth2Authentication oAuth2Auth = tokenStore.readAuthentication(token);
            Authentication authentication = oAuth2Auth.getUserAuthentication();

            map.put(OAuth2Utils.CLIENT_ID, oAuth2Auth.getOAuth2Request().getClientId());
            map.put(OAuth2Utils.GRANT_TYPE, oAuth2Auth.getOAuth2Request().getGrantType());

            if (authentication instanceof UsernamePasswordAuthenticationToken) {
                UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
                if (authenticationToken.getPrincipal() instanceof SecurityUser) {
                    SecurityUser user = (SecurityUser) authenticationToken.getPrincipal();
                    map.put("username", user.getUsername() + "");
                }
            } else if (authentication instanceof PreAuthenticatedAuthenticationToken) {
                //刷新token方式
                PreAuthenticatedAuthenticationToken authenticationToken = (PreAuthenticatedAuthenticationToken) authentication;
                if (authenticationToken.getPrincipal() instanceof SecurityUser) {
                    SecurityUser user = (SecurityUser) authenticationToken.getPrincipal();
                    map.put("username", user.getUsername() + "");
                }
            }
            list.add(map);
        }

        Page result = new Page(MapUtil.getInt(params, CommonConstants.CURRENT), MapUtil.getInt(params, CommonConstants.SIZE));
        result.setRecords(list);
        result.setTotal(Long.valueOf(redisTemplate.keys(SecurityConstants.TOKEN_PERFIX_ACCESS + "*").size()));
        return RestResult.data(result);

    }


    private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
        List<String> result = new ArrayList<>();
        int tmpIndex = 0;
        int startIndex = (pageNum - 1) * pageSize;
        int end = pageNum * pageSize;

        assert cursor != null;
        while (cursor.hasNext()) {
            if (tmpIndex >= startIndex && tmpIndex < end) {
                result.add(cursor.next().toString());
                tmpIndex++;
                continue;
            }
            if (tmpIndex >= end) {
                break;
            }
            tmpIndex++;
            cursor.next();
        }
        return result;
    }

}
